﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace SimpleServiceBus.Utilities.ServiceManager
{
    public class LocalServiceManager : BackgroundServiceBase
    {
        private readonly ReaderWriterLock _rwLock = new ReaderWriterLock();
        private readonly Dictionary<Type, object> _serviceTable = new Dictionary<Type, object>();

        private Func<object, Exception, bool> _startServiceErrorCallback;
        private Func<object, Exception, bool> _stopServiceErrorCallback;

        /// <summary>
        /// Starts the service container and all child services. If an error is encountered starting
        /// a particular service, the errorCallback will be called. Returning true from the callback
        /// will cause the exception to be rethrown, calling false will suppress the exception and
        /// allow the service startup process to continue.
        /// </summary>
        /// <param name="errorCallback"></param>
        public virtual void Start(Func<object, Exception, bool> errorCallback)
        {
            _startServiceErrorCallback = errorCallback;
            Start();
        }

        /// <summary>
        /// Stop the service container and all child services. If an error is encountered stopping
        /// a particular service, the errorCallback will be called. Returning true from the callback
        /// will cause the exception to be rethrown, calling false will suppress the exception and
        /// allow the service shutdown process to continue.
        /// </summary>
        /// <param name="errorCallback"></param>
        public virtual void Stop(Func<object, Exception, bool> errorCallback)
        {
            _stopServiceErrorCallback = errorCallback;
            Stop();
        }

        public virtual void RegisterService<T>(T serviceImplementation) where T : class
        {
            if (serviceImplementation == null)
                throw new ArgumentNullException("serviceImplementation");

            if (typeof (T).IsAssignableFrom(serviceImplementation.GetType()) == false)
                throw new ArgumentException(
                    "The service implemenation provided is not assignable from the type provided to the generic argument when invoking RegisterService.");

            if (IsRunning)
                StartLocalService(serviceImplementation);

            using (new WriterLock(_rwLock, TimeSpan.FromSeconds(10)))
            {
                    _serviceTable[typeof (T)] = serviceImplementation;
            }
        }

        public virtual bool HasService<T>()
        {
            return _serviceTable.ContainsKey(typeof (T));
        }

        public virtual T GetService<T>()
        {
            using (new ReaderLock(_rwLock, TimeSpan.FromSeconds(10)))
            {
                Object service;
                if (_serviceTable.TryGetValue(typeof (T), out service))
                    return (T) service;
            }
            return default(T);
        }

        protected override void StartService()
        {
            foreach (object service in _serviceTable.Values)
            {
                StartLocalService(service);
            }
        }

        /// <summary>
        /// Shuts down service.
        /// </summary>
        protected override void ShutDownService()
        {
            foreach (object service in _serviceTable.Values)
            {
                StopLocalService(service);
            }
        }

        protected virtual void StartLocalService(object service)
        {
            try
            {
                var bgService = service as IBackgroundService;
                if (bgService != null)
                    bgService.Start();
            }
            catch (Exception ex)
            {
                if (_startServiceErrorCallback == null || _startServiceErrorCallback(service, ex))
                    throw;
            }
        }

        protected virtual void StopLocalService(object service)
        {
            try
            {
                var bgService = service as IBackgroundService;
                if (bgService != null)
                    bgService.Stop();
            }
            catch (Exception ex)
            {
                if (_stopServiceErrorCallback == null || _stopServiceErrorCallback(service, ex))
                    throw;
            }
        }
    }
}